package protogps;

import java.util.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.location.*;
import javax.microedition.io.file.FileConnection;
import javax.microedition.io.Connector;
import henson.midp.*;
import java.io.*;

/**
 * @author rafael
 */
public class ProtoGPSMIDlet extends MIDlet
        implements CommandListener, LocationListener {

    private boolean midletPaused = false;
    LocationProvider lp;
    Location l1, l2;
    FileConnection arqSaida;
    OutputStream os;
    Location locationAnterior;
    int contadorLocations;
    long tempoInicioListener;
    private Command exitCommand;
    private Command iniciaProviderCommand;
    private Command mostraStatusCommand;
    private Command guardaLocation1Command;
    private Command guardaLocation2Command;
    private Command paraListenerCommand;
    private Command iniciaListenerCommand;
    private Command mostraMapaRegiaoCommand;
    private Form form;
    private StringItem stringItem;

    public ProtoGPSMIDlet() {
        iniciaForm();
    }

    private String getHoraData() {

        Calendar cal = Calendar.getInstance();

        // montando a hora e a data p/ nomear o arquivo
        String horadata = String.valueOf(cal.get(Calendar.HOUR_OF_DAY)) +
                "_" + String.valueOf(cal.get(Calendar.MINUTE)) + "-" +
                String.valueOf(cal.get(Calendar.DATE)) + "_" +
                String.valueOf(cal.get(Calendar.MONTH)) + "_" +
                String.valueOf(cal.get(Calendar.YEAR));

        return horadata;
    }

    private void guardaLocation1() {
        try {
            long tempoInicio = new Date().getTime();
            l1 = lp.getLocation(600);
            long tempoFinal = new Date().getTime();
            String lat = String.valueOf(l1.getQualifiedCoordinates().getLatitude());
            String lon = String.valueOf(l1.getQualifiedCoordinates().getLongitude());
            String alt = String.valueOf(l1.getQualifiedCoordinates().getAltitude());
            escreve("\nL1:");
            escreve("lat=" + lat);
            escreve("lon=" + lon);
            escreve("alt=" + alt);
            escreve("Tempo de resposta: " + (tempoFinal - tempoInicio) + "\n");
        } catch (Exception e) {
            escreve(e.toString());
        }
    }

    private void guardaLocation2() {
        try {
            long tempoInicio = new Date().getTime();
            l2 = lp.getLocation(600);
            long tempoFinal = new Date().getTime();
            String lat = String.valueOf(l2.getQualifiedCoordinates().getLatitude());
            String lon = String.valueOf(l2.getQualifiedCoordinates().getLongitude());
            String alt = String.valueOf(l2.getQualifiedCoordinates().getAltitude());
            escreve("\nL2:");
            escreve("lat=" + lat);
            escreve("lon=" + lon);
            escreve("alt=" + alt);
            escreve("Tempo de resposta: " + (tempoFinal - tempoInicio) + "\n");
        } catch (Exception e) {
            escreve(e.toString());
        }
    }

    private void iniciaForm() {
        exitCommand = new Command("Tchau", Command.EXIT, 0);
        stringItem = new StringItem("Info:", "");
        form = new Form("ProtoGPS!", new Item[]{stringItem});
        iniciaProviderCommand = new Command("Inicia Provider", Command.OK, 0);
        mostraStatusCommand = new Command("Status", Command.OK, 0);
        guardaLocation1Command = new Command("Guarda Location 1", Command.OK, 0);
        guardaLocation2Command = new Command("Guarda Location 2", Command.OK, 0);
        iniciaListenerCommand = new Command("Inicia Listener", Command.OK, 0);
        paraListenerCommand = new Command("Para Listener", Command.OK, 0);
        mostraMapaRegiaoCommand = new Command("Mapa Região", Command.OK, 0);

        form.addCommand(exitCommand);
        form.addCommand(iniciaProviderCommand);
        form.addCommand(mostraStatusCommand);
        form.addCommand(guardaLocation1Command);
        form.addCommand(guardaLocation2Command);
        form.addCommand(iniciaListenerCommand);
        form.addCommand(paraListenerCommand);
        form.addCommand(mostraMapaRegiaoCommand);
        form.setCommandListener(this);
    }

    private void iniciaListener() {

        try {
            String nomeArqSaida = "output." + getHoraData() + ".txt";
            arqSaida = (FileConnection) Connector.open(
                    //System.getProperty("fileconn.dir.memorycard") + nomeArqSaida, <-- não funciona no novo firmware do n95
                    "file:///e:/" + nomeArqSaida,
                    Connector.READ_WRITE);
            if (!arqSaida.exists()) {
                arqSaida.create();
            }
            os = arqSaida.openOutputStream();
        } catch (Exception e) {
            escreve(e.toString());
        }

        new Thread() { // inicia o listener em uma nova thread

            public void run() {
                lp.setLocationListener(ProtoGPSMIDlet.this, 5, 5, 5);
            }
        }.start();

        escreve("LocationListener set");

        // iniciando a variável- assim, a primeira location obtida possuirá id = 1
        contadorLocations = 0;

        // tempo inicial
        tempoInicioListener = System.currentTimeMillis();
    }

    private void iniciaProvider() {
        escreve("Iniciando LocationProvider...");
        try {
            lp = LocationProvider.getInstance(null);
        } catch (Exception e) {
            escreve(e.toString());
        }
    }

    private void mostraStatus() {
        if (lp.getState() == LocationProvider.AVAILABLE) {
            escreve("LP: AVAILABLE");
        } else if (lp.getState() == LocationProvider.OUT_OF_SERVICE) {
            escreve("LP: OUT_OF_SERVICE");
        } else if (lp.getState() == LocationProvider.TEMPORARILY_UNAVAILABLE) {
            escreve("LP: TEMPORARILY_UNAVAILABLE");
        }
    }

    private void mostraImagemMapa(Location loc) {
        final GoogleMaps gm = new GoogleMaps("ABQIAAAAAMK9VqyOXAwpe3uy0orWvhR_JaVGHEkDw3LFEfAVWdGRLfRUlBT4V-YRCavB7qApq-M-SEUFMnyTlQ");
        final Displayable d = Display.getDisplay(this).getCurrent();

        final double lat = loc.getQualifiedCoordinates().getLatitude();
        final double lon = loc.getQualifiedCoordinates().getLongitude();

        new Thread() {
            public void run() {
                Image mapa = null;
                try {
                    mapa = gm.retrieveStaticImage(d.getWidth(), d.getHeight(), lat, lon, 17, "png32");
                } catch (IOException ex) {
                    escreve("Mapa erro: " + ex.getMessage());
                }
                form.append(mapa);
            }
        }.start();
        
    }

    private void initialize() {
    }

    public void startMIDlet() {
        switchDisplayable(null, form);
    }

    public void resumeMIDlet() {
    }

    public void switchDisplayable(Alert alert, Displayable nextDisplayable) {
        Display display = Display.getDisplay(this);
        if (alert == null) {
            display.setCurrent(nextDisplayable);
        } else {
            display.setCurrent(alert, nextDisplayable);
        }
    }

    public void commandAction(Command command, Displayable displayable) {

        if (displayable == form) {
            if (command == exitCommand) {
                exitMIDlet();
            } else if (command == guardaLocation1Command) {
                guardaLocation1();
            } else if (command == guardaLocation2Command) {
                guardaLocation2();
            } else if (command == iniciaListenerCommand) {
                iniciaListener();
            } else if (command == paraListenerCommand) {
                paraListener();
            } else if (command == iniciaProviderCommand) {
                iniciaProvider();
            } else if (command == mostraStatusCommand) {
                mostraStatus();
            } else if (command == mostraMapaRegiaoCommand) {
                mostraImagemMapa(l1);
            }
        }
    }

    public void exitMIDlet() {
        switchDisplayable(null, null);
        destroyApp(true);
        notifyDestroyed();
    }

    public void startApp() {
        if (midletPaused) {
            resumeMIDlet();
        } else {
            initialize();
            startMIDlet();
        }
        midletPaused = false;
    }

    public void pauseApp() {
        midletPaused = true;
    }

    public void destroyApp(boolean unconditional) {
    }

    private void escreve(String s) {
        stringItem.setText(stringItem.getText() +
                "\n" + s);
    }


    //métodos da interface LocationListener
    public synchronized void locationUpdated(LocationProvider arg0, Location arg1) {
        //escreve("locationupdated!");
        contadorLocations++;
        if (arg1 != null && locationAnterior != null) {
            if (arg1.isValid() && locationAnterior.isValid()) {
                try {
                    String id = String.valueOf(contadorLocations);
                    String tempo = String.valueOf(System.currentTimeMillis() - tempoInicioListener);
                    String lat = String.valueOf(arg1.getQualifiedCoordinates().getLatitude());
                    String lon = String.valueOf(arg1.getQualifiedCoordinates().getLongitude());
                    String alt = String.valueOf(arg1.getQualifiedCoordinates().getAltitude());
                    String difmVincentyMethod = String.valueOf(calculaDistEmM(arg1, locationAnterior));
                    String difmDistanceMethod = String.valueOf(
                            arg1.getQualifiedCoordinates().distance(
                            locationAnterior.getQualifiedCoordinates()));

                    String precisaoH = String.valueOf(arg1.getQualifiedCoordinates().getHorizontalAccuracy());
                    String precisaoV = String.valueOf(arg1.getQualifiedCoordinates().getVerticalAccuracy());

                    String locationMethod = String.valueOf(arg1.getLocationMethod());

                    String praEscrever = id + "\t" +
                            tempo + "\t" +
                            lat + "\t" +
                            lon + "\t" +
                            alt + "\t" +
                            difmVincentyMethod + "\t" +
                            difmDistanceMethod + "\t" +
                            precisaoH + "\t" +
                            precisaoV + "\t" +
                            locationMethod + "\n";

                    //escrever
                    os.write(praEscrever.getBytes());
                    os.flush();
                } catch (Exception e) {
                    escreve(e.toString());
                }
            } else if (!arg1.isValid()) {
                escreve(arg1.getExtraInfo("text/plain"));
            }
        }
        if (arg1.isValid()) {
            locationAnterior = arg1;
        }
    }

    public void providerStateChanged(LocationProvider arg0, int arg1) {
        escreve("providerStateChanged!");
    }
    //fim métodos da interface

    private double calculaDistEmM(Location loc1, Location loc2) {
        // utilizando a fórmula de Vincenty,
        // baseado na implementação em javascript de:
        // http://www.movable-type.co.uk/scripts/latlong-vincenty.html

        double lat1 = loc1.getQualifiedCoordinates().getLatitude();
        double lat2 = loc2.getQualifiedCoordinates().getLatitude();
        double lon1 = loc1.getQualifiedCoordinates().getLongitude();
        double lon2 = loc2.getQualifiedCoordinates().getLongitude();

        double a = 6378137, b = 6356752.3142, f = 1 / 298.257223563;  // WGS-84 ellipsiod
        double L = toRad(lon2 - lon1);
        double U1 = Float11.atan((1 - f) * Math.tan(toRad(lat1)));
        double U2 = Float11.atan((1 - f) * Math.tan(toRad(lat2)));
        double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
        double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);

        /* declarando fora */
        double sinLambda,
                cosLambda,
                sinSigma,
                cosSigma,
                sigma,
                sinAlpha,
                cosSqAlpha,
                cos2SigmaM;
        /* fim declarando fora */

        double lambda = L, lambdaP;
        int iterLimit = 20;

        do {
            sinLambda = Math.sin(lambda);
            cosLambda = Math.cos(lambda);
            sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) +
                    (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
            if (sinSigma == 0) {
                return 0;  // co-incident points
            }
            cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
            sigma = Float11.atan2(sinSigma, cosSigma);
            sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
            cosSqAlpha = 1 - sinAlpha * sinAlpha;
            cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
            if (Double.isNaN(cos2SigmaM)) {
                cos2SigmaM = 0;  // equatorial line: cosSqAlpha=0 (§6)
            }
            double C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
            lambdaP = lambda;
            lambda = L + (1 - C) * f * sinAlpha *
                    (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
        } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);

        if (iterLimit == 0) {
            return Double.NaN; // formula failed to converge
        }

        double uSq = cosSqAlpha * (a * a - b * b) / (b * b);
        double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
        double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
        double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) -
                B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
        double s = b * A * (sigma - deltaSigma);

        return s;
    }

    private void paraListener() {
        new Thread() {

            public void run() {
                lp.setLocationListener(null, 5, -1, -1);
            }
        }.start();
        try {
            os.close();
            arqSaida.close();
        } catch (Exception e) {
            escreve(e.toString());
        }
        escreve("LocationListener unset");
    }

    private double toRad(double d) {
        return d * Math.PI / 180;
    }
}
